/***
    Amrita ITEWS - Copyright (C) 2009 Amrita Vishwa Vidyapeetham, Amritapuri.
                                       (http://www.amrita.edu)
    ***************************************************************************
    This file is part of the Amrita ITEWS distribution.
    Amrita ITEWS is free software; you can redistribute it and/or modify it
    under the terms of the GNU General Public License (version 2) as published
    by the Free Software Foundation AND MODIFIED BY the Amrita ITEWS exception.
    ***NOTE*** The exception to the GPL is included to allow you to distribute
    a combined work that includes Amrita ITEWS without being obliged to provide
    the source code for proprietary components outside of the Amrita ITEWS
    software. Amrita ITEWS is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
    Public License for more details. You should have received a copy of the GNU
    General Public License and the Amrita ITEWS license exception along with
    Amrita ITEWS if not then it can be viewed here:
    http://itews.amrita.ac.in/license.html.

    Note: This software is closely coupled to FreeRTOS and hence the
    licensing terms of FreeRTOS would apply.


    Documentation, latest information, license and contact details are at:
    http://itews.amrita.ac.in/


    Amrita ITEWS source code can be found at:
    http://code.google.com/p/itews


    The initial code-base of Amrita ITEWS was developed by Amrita Vishwa
    Vidyapeetham as part of the project titled,"Intelligent & Interactive
    Telematics using Emerging Wireless Technologies for Transport Systems
    (ITEWS)" sponsored by Technology Information Forecasting and Assessment
    Council (TIFAC), India.
***/

/***************************************************************************
Module: igpsu.c
Description: driver file for the ssp port for LED Display board
Version     Date           Author    Description
0.1         5Sep2007       Sriram    Driver for Ignition Power Supply Unit
****************************************************************************/

// Include files
#include <lpc21xx.h>
#include <FreeRTOS.h>
#include <task.h>
#include <queue.h>
#include <errno.h>
#include <board_config.h>
#include <res.h>
#include <typedefs.h>
#include <igpsu.h>
#include <sys.h>

// defines
#define IGPSU_FREE      0
#define IGPSU_OPEN      1
#define IGPSU_BUSY      2
//defines for System status
#define IGPSU_ACTIVE    0
#define IGPSU_PWR_DWN   1

//times are in millis
#define IGPSU_MIN_DISCHARGE_TIME 10
#define IGPSU_MAX_CHARGE_TIME 1000

// Global Variables
uint32 igpsu_battery_voltage;

static int32   vector;
static int8    state;
static wait_struct_t *curr_ws;
static uint32 igpsu_op_status;
static uint32   igpsu_discharge_begin_ts;

static int8    battery_voltage_rating = IGPSU_DEFAULT_BATTERY_VOLTAGE_RATING;

static void igpsu_ccp_isr( void ) __attribute__ ((naked));

static int32 igpsu_counter_to_voltage(uint32 counter);

// Initilization Function for IGPSU pins and interrupt vector

int32 igpsu_open()
{
    int32 status=1;

    if(IGPSU_OPEN == state)
    {
         return -EBUSY;
    }

    taskENTER_CRITICAL();
    {
        if(IGPSU_BUSY == state)
        {
            status = -EBUSY;
        }else
        {
            state = IGPSU_BUSY;
        }
    }

    taskEXIT_CRITICAL();

    if(status < 0)
    {
        return status;
    }

    // Acquiring the pins and VIC vector
    taskENTER_CRITICAL();
    {
        if(res_port0_acquire_(__FILE__  ,
                BIT( PIN_RELAY1_CNTRL ) |
                BIT( PIN_RELAY2_CNTRL ) |
                BIT( PIN_IGN_DETECT ) |
                BIT( PIN_BUZZER )     |
                BIT( PIN_PWR_CAP_24V ) |
                BIT( PIN_PWR_CAP_12V ) |
                BIT( PIN_12V_PWR_STS ) |
                BIT( PIN_24V_PWR_STS ) |
                BIT( PIN_LED0 ) |
                BIT( PIN_LED1 ) |
                BIT( PIN_LED2 )
                  ) )
        {
                if(!(vector = res_vic_vect_acquire_any_(__FILE__))) // acquiring vector
                {
                    res_port0_release_(__FILE__ ,
                                        BIT( PIN_RELAY1_CNTRL ) |
                                        BIT( PIN_RELAY2_CNTRL ) |
                                        BIT( PIN_IGN_DETECT ) |
                                        BIT( PIN_BUZZER ) |
                                        BIT( PIN_PWR_CAP_24V ) |
                                        BIT( PIN_PWR_CAP_12V ) |
                                        BIT( PIN_12V_PWR_STS ) |
                                        BIT( PIN_24V_PWR_STS ) |
                                        BIT( PIN_LED0 ) |
                                        BIT( PIN_LED1 ) |
                                        BIT( PIN_LED2 )
                                        );
                    status = -EBUSY;
                }
        }else
        {
            status = -EBUSY;
        }
    }
    taskEXIT_CRITICAL();

    if(status < 0)
    {
        return status;
    }

    // Selecting the function for the ping. PIN_FN0 is for GPIO
    PINSEL(PIN_RELAY1_CNTRL, PIN_FN0);
    PINSEL(PIN_RELAY2_CNTRL, PIN_FN0);
    PINSEL(PIN_IGN_DETECT, PIN_FN0 );
    PINSEL(PIN_BUZZER, PIN_FN0 );
    PINSEL(PIN_PWR_CAP_24V, PIN_FN0 );
    PINSEL(PIN_PWR_CAP_12V, PIN_FN0 );
    PINSEL(PIN_12V_PWR_STS, PIN_FN2 );//CCP function
    PINSEL(PIN_24V_PWR_STS, PIN_FN2 );//CCP function
    PINSEL(PIN_LED0, PIN_FN0 );
    PINSEL(PIN_LED1, PIN_FN0 );
    PINSEL(PIN_LED2, PIN_FN0 );

    // Selecting the direction for each pins
    PORT0_DIR_OUTPUT(PIN_RELAY1_CNTRL);
    PORT0_DIR_OUTPUT(PIN_RELAY2_CNTRL);
    PORT0_DIR_OUTPUT(PIN_BUZZER);
    PORT0_DIR_OUTPUT(PIN_PWR_CAP_24V);
    PORT0_DIR_OUTPUT(PIN_PWR_CAP_12V);
    PORT0_DIR_OUTPUT(PIN_LED0);
    PORT0_DIR_OUTPUT(PIN_LED1);
    PORT0_DIR_OUTPUT(PIN_LED2);
    PORT0_DIR_INPUT(PIN_12V_PWR_STS);
    PORT0_DIR_INPUT(PIN_24V_PWR_STS);
    PORT0_DIR_INPUT(PIN_IGN_DETECT);

    /*
     * Set these high so that voltage-sensing capacitor
     * is kept discharged and ready to sense the voltage
     * at any time.
     */
    PORT0_SET(PIN_PWR_CAP_24V);
    PORT0_SET(PIN_PWR_CAP_12V);

    igpsu_discharge_begin_ts = sys_timestamp_ms();

    T1_TCR = 0;//disable counter

    if(battery_voltage_rating == 12)
    {
        T1_CCR = 0x06;//capture on falling edge of CAP1.0 + enable interrupt
        T1_IR = 0x10;//interrupt on CAP1.0
    }else
    {
        T1_CCR = 0x30;//capture on falling edge of CAP1.1 + enable interrupt
        T1_IR = 0x20;//interrupt on CAP1.1
    }

    T1_PR = 600;//10 us input clock

    T1_CTCR = 0;//Timer mode

    VIC_VECT_ENABLE(vector, VIC_CH_TIM1,igpsu_ccp_isr);
    VIC_CH_IRQ_ENABLE(VIC_CH_TIM1);

    state = IGPSU_OPEN;

    return status;

}

// Function to switch on the relay. Given the relay number the
// corresponding relay will be switched on.
int32 igpsu_relay_on(uint8 rly_number)
{
    if( rly_number == 0)
    {
        PORT0_SET(PIN_RELAY1_CNTRL);
    }
    else if (rly_number == 1)
    {
        PORT0_SET(PIN_RELAY2_CNTRL);
    }
    else
    {
        return -EINVAL;
    }

    return 0;
}

// Function to switch off the relay. Given the relay number the
// corresponding relay will be switched off.
int32 igpsu_relay_off(uint8 rly_number)
{
    if(rly_number == 0)
    {
        PORT0_CLR(PIN_RELAY1_CNTRL);
    }
    else if (rly_number == 1)
    {
        PORT0_CLR(PIN_RELAY2_CNTRL);
    }
    else
    {
        return -EINVAL;
    }

    return 0;
}

// Function to switch on the leds. Given the led number the
// corresponding led will be switched on. LED1 is red led,
// LED2 is green led and LED3 is amber LED.
int32 igpsu_led_on(uint8 led_number)
{
    if( led_number == 0)
    {
        PORT0_SET(PIN_LED0);
    }
    else if (led_number == 1)
    {
        PORT0_SET(PIN_LED1);
    }
    else if (led_number == 2)
    {
        PORT0_SET(PIN_LED2);
    }
    else
    {
        return -EINVAL;
    }

    return 0;
}

// Function to switch off the leds. Given the led number the
// corresponding led will be switched off. LED1 is red led,
// LED2 is green led and LED3 is amber LED.
int32 igpsu_led_off(uint8 led_number)
{
    if( led_number == 0)
    {
        PORT0_CLR(PIN_LED0);
    }
    else if (led_number == 1)
    {
        PORT0_CLR(PIN_LED1);
    }
    else if (led_number == 2)
    {
        PORT0_CLR(PIN_LED2);
    }
    else
    {
        return -EINVAL;
    }
    return 0;
}

// Function to switch on the buzzer
int32 igpsu_buzzer_on()
{
    PORT0_SET(PIN_BUZZER);

    return 0;
}

// Function to switch off the buzzer
int32 igpsu_buzzer_off()
{
    PORT0_CLR(PIN_BUZZER);
    return 0;
}

// Read the Ignition Detec Port Pin to determine whether the system is
// on or off.
int32 igpsu_ignition_is_on()
{
    if (PORT0_IO_READ(PIN_IGN_DETECT))
    {
       return 0;
    }
    else
    {
       return 1;
    }
}

void igpsu_powerdown()
{
    PINSEL(PIN_IGN_INTR, PIN_FN3 );
    SCB_EXTWAKE =  0x02;//Enables Wakeup on EXT1
}

void igpsu_powerup()
{
    PINSEL(PIN_IGN_DETECT, PIN_FN0 );
    SCB_EXTWAKE = 0x00;
    SCB_EXTINT = 0x02;//Clear the EXT1 interrupt bit
}

int32 igpsu_get_battery_voltage()
{
    int32 volt;
    uint32 ts;
    /*
     * By default SET PIN_PWR_CAP_xx is high
     * This will make sure capacitor is discharged.
     *
     * Check if PIN_PWR_CAP_xx has been high for
     * enough time for capacitor to fully discharge
     * or else wait for some time.
     * Initialize the Capture-Compare-Timer (Timer1)
     * SET PIN_PWR_CAP_xx low
     * This will start charging the capacitor
     * Once capacitor reaches the comparator threshold
     * (for 12v systems - 1.04v, for 24v systems -2.62v )
     * the comparator output will go high.
     * This will generate a low on PIN_xx_PWR_STS and
     * cause a capture interrupt to occur.
     * The capture count represents the voltage.
     * SET PIN_PWR_CAP_xx high again
     */

    ts = sys_timestamp_ms() - igpsu_discharge_begin_ts;
    if(ts < IGPSU_MIN_DISCHARGE_TIME)
    {
        sys_msleep(IGPSU_MIN_DISCHARGE_TIME);
    }

    curr_ws = CURR_WAIT_STRUCT();

    igpsu_op_status = 0;

    taskENTER_CRITICAL();
    {
        T1_TCR = 0x3;//enable and reset timer
        T1_TCR = 0x1;//done with reset

        if(battery_voltage_rating == 12)
        {
            PORT0_CLR(PIN_PWR_CAP_12V);
        }else
        {
            PORT0_CLR(PIN_PWR_CAP_24V);
        }
    }
    taskEXIT_CRITICAL();

    TASK_WAIT_TIMEOUT(curr_ws, &igpsu_op_status, IGPSU_MAX_CHARGE_TIME);

    PORT0_SET(PIN_PWR_CAP_24V);
    PORT0_SET(PIN_PWR_CAP_12V);

    if(igpsu_op_status == 0)
    {
        //interrupt didn't occur and we timed out
        //so we clear things up here
        T1_TCR = 0x0;//disable timer
        if(battery_voltage_rating == 12)
        {
            T1_IR = 0x10;
        }else
        {
            T1_IR = 0x20;
        }
    }
    igpsu_discharge_begin_ts = sys_timestamp_ms();

    volt = igpsu_counter_to_voltage(igpsu_op_status);

    portENTER_CRITICAL()
    {
        igpsu_battery_voltage = volt;
    }
    portEXIT_CRITICAL();

    return igpsu_battery_voltage;
}

static void igpsu_ccp_isr( )
{
    portENTER_SWITCHING_ISR();

    if(battery_voltage_rating == 12)
    {
        igpsu_op_status = T1_CR0;
        T1_IR = 0x10;
    }else
    {
        igpsu_op_status = T1_CR1;
        T1_IR = 0x20;
    }

    T1_TCR = 0x0;//disable timer

    TASK_NOTIFY_FROM_ISR(curr_ws,&igpsu_op_status);

    VIC_CLEAR_IRQ();

    portEXIT_SWITCHING_ISR(1);

}

#define LOWEST_TBL_MV (10*1000)

static const int32 counter_to_voltage_tbl[] =
{
    1885,//10v
    1497,//10.5v
    1226,//11v
    1041,//11.5v
    922,//12v
    822,//12.5v
    740,//13v
    675 //13.5v
};

static int32 igpsu_counter_to_voltage(uint32 counter)
{
    int32 battery_mV = LOWEST_TBL_MV;
    int i;

#define step_mV  500

    if(counter == 0)
    {
        return 0;
    }
    if(counter >= counter_to_voltage_tbl[0])
    {
        return battery_mV;
    }

    for(i=0;i<sizeof(counter_to_voltage_tbl)/sizeof(uint32)-1;i++)
    {
        if( (counter <= counter_to_voltage_tbl[i]) &&
            (counter >= counter_to_voltage_tbl[i+1]) )
        {
            if( (counter_to_voltage_tbl[i] - counter) >
                (counter - counter_to_voltage_tbl[i+1]) )
            {
                battery_mV += step_mV;
            }
            break;
        }
        battery_mV += step_mV;
    }

    return battery_mV;
}

